package com.oazon.moco.lan.core.udp;

import android.content.Context;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import com.oazon.common.Logger;
import com.oazon.common.Settings;
import com.oazon.common.TaskHelper;
import com.oazon.common.TaskHelper.RunnableWithName;
import com.oazon.common.util.StringUtils;
import com.oazon.common.util.Utils;
import com.oazon.moco.lan.LanServerManage;

class DiscoveryReceiver {
    private static final String TAG = "DiscoveryReceiver";
    
    private static final int BUFFER_LENGTH = 1024;
    
    public interface DiscoveryPacketListener {
        void onPacket(String fromIp, String packet);
    }
    
    private DatagramSocket mBroadReceiver;
    private BlockingQueue<DatagramPacket> mPackets;
    
    private List<DiscoveryPacketListener> mListeners;
    private final AtomicBoolean mRunning = new AtomicBoolean(false);
    private Context mContext;
    
    public DiscoveryReceiver(Context context){
        mListeners = new CopyOnWriteArrayList<DiscoveryPacketListener>();
        mPackets = new LinkedBlockingQueue<DatagramPacket>();
        mContext = context;
    }
    
    public void addListener(DiscoveryPacketListener listener) {
        mListeners.add(listener);
    }
    
    public void removeListener(DiscoveryPacketListener listener) {
        mListeners.remove(listener);
    }
    
    public void start(int port) {
        if (!mRunning.compareAndSet(false, true))
            return;
        startBroadcastReceiver(port);
    }
    
    public void stop() {
        if (!mRunning.compareAndSet(true, false))
            return;
        
        mPackets.clear();
        if (mBroadReceiver != null)
            mBroadReceiver.close();
    }
    
    private void startBroadcastReceiver(int port) {
        try {
            mBroadReceiver = new MulticastSocket(port);
            mBroadReceiver.setReuseAddress(true);
            mBroadReceiver.setSoTimeout(0);
        } catch (Exception e) {
            Logger.w(TAG, "start packet receiver failed!", e);
            return;
        } // for receiving 

        TaskHelper.execZForSDK(new RunnableWithName("TS.Lan.HandlePacket") {
            @Override
            public void execute() {
                try {
                    handleBroadPacket();
                } finally {
                    stop();
                }
            }
        });

        TaskHelper.execZForSDK(new RunnableWithName("TS.Lan.DoFireReceived") {

            @Override
            public void execute() {
                doFirePacket();
            }
        });
    }
    
    private void handleBroadPacket() {
        while (mRunning.get()) {
            try {
                byte[] buffer = new byte[BUFFER_LENGTH];
                DatagramPacket packet = new DatagramPacket(buffer, BUFFER_LENGTH);
                mBroadReceiver.receive(packet);
                mPackets.add(packet);
            } catch (SocketTimeoutException e) {
                continue;
            } catch (SocketException e) {
                Logger.e(TAG, e.toString());
            } catch (IOException e) {
                Logger.e(TAG, e.toString());
            }
        }
        
        Logger.w(TAG, "handle udp thread is over");
    }
    
    private void doFirePacket() {
        Logger.i(TAG, "Start fire on received packets!");
        while (mRunning.get()) {
            try {
                DatagramPacket packet = mPackets.poll(2, TimeUnit.SECONDS);
                if (packet == null)
                    continue;

                int length = packet.getLength();
                if (length >= BUFFER_LENGTH) {
                    Logger.i(TAG, "Received long message, length is over the max!");
                    continue;
                }

                byte[] datas = packet.getData();
                String fromIp = packet.getAddress().getHostAddress();
                String localIp = Settings.getInstance(mContext).get(LanServerManage.LOCAL_IP);//Utils.getLocalIp();
                if (StringUtils.isEquals(fromIp, localIp))
                		continue;
                String msgstr = new String(datas, 0, length);

                fireOnPacket(fromIp, msgstr);
            } catch (InterruptedException e) {
                continue;
            }
        }
        Logger.i(TAG, "Completed fire on received packets!");
    }
    
    private void fireOnPacket(String fromIp, String packet) {
        for (DiscoveryPacketListener listener : mListeners) {
            try {
                listener.onPacket(fromIp, packet);
            }catch (Exception e){}
        }
    }
}
